08. Thread Pools and Executors
Thread Pools and Executors
In this section, you'll learn why and how we use thread pools in Java.
ND079 JPND C2 L05 A07 Thread Pools And Executors
What is a Thread Pool?
A thread pool is a collection of threads that is used to efficiently execute and manage asynchronous work.
Thread pools reduce the cost of using threads by storing them in a worker thread pool. That way, your program can reuse existing threads instead of creating a new thread for each piece of work that needs to be done.
New work is added to a work queue, where it waits for a pooled thread to become available. When a thread is available, it will remove work from the queue, and do the work.
Benefits of Thread Pools
Thread pools have several advantages over creating and using Thread
objects directly:
Limits the number of threads used by the program, and prevents the number of threads from growing in an unbounded manner.
Reuses worker threads, which reduces the time and memory spent creating new threads.
SOLUTION:
- The program can slow to a crawl because it is trying to do too much work at once.
- The program can run out of memory and crash.
- The program may end up preventing other programs on the computer from creating operating system threads.
Creating Thread Pools
In Java, thread pools are created using the Executors
API. Here are some examples:
A thread pool with only one thread:
ExecutorService pool = Executors.newSingleThreadExecutor();
A thread pool that reuses threads but does not limit the number of threads it can create:
ExecutorService pool = Executors.newCachedThreadPool();
A thread pool that reuses threads and limits the number of threads to 12:
ExecutorService pool = Executors.newFixedThreadPool(12);
SOLUTION:
If a worker thread is idle, the thread pool can reuse it instead of creating a brand new thread.Submitting Asynchronous Work
Thread pools have several methods that let you submit work to be executed asynchronously:
Submits a
Runnable
with no return value, and returns aFuture
:Future> print = pool.submit(() -> System.out.println("foo"));
Submits a
Runnable
and returnsvoid
:pool.execute(() -> System.out.println("foo"));
Submits a
Callable
, whose return value will be accessible via theFuture
:Future
pathFuture = pool.submit(() -> downloadFile());
Futures
ND079 JPND C2 L05 A08 Futures
A future is a reference to the result of an asynchronous computation.
Java uses the Future
class to represent this concept.
If the asynchronous computation is done, calling the get()
method will return the result of the computation. If the computation is not done, calling get()
will cause the program to stop and wait for the computation to finish.
Future
s are parameterized. Calling the get()
method of a Future<Map>
will return a Map
. Or, if the result is a List
, calling get()
on a Future<List>
will return a List
result, and so on.
SOLUTION:
2 threadsJoining Asynchronous Work
You learned that calling Future.get()
on a future returned from a thread pool will cause the program to stop and wait for the parallel thread to finish its computation. This process of waiting for asynchronous work is called joining. That's why the Thread
class has a method called join()
.
What happens if we don't have a Thread
or Future
to explicitly join? Here's one solution to the problem:
CountDownLatch latch = new CountDownLatch(1);
pool.execute(() -> {
System.out.println("foo");
latch.countDown();
});
latch.await();
In this code, CountDownLatch
helps wait for the asynchronous work to complete even though we don't have a Thread
or Future
.